package jflowmap.views.heatmap;

import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;

import javax.swing.SwingUtilities;

import jflowmap.ColorSchemes;
import jflowmap.model.geo.MapProjection;
import jflowmap.model.graph.Graph;
import jflowmap.model.map.GeoMap;
import jflowmap.model.ontology.SelectionDefinition;
import jflowmap.model.ontology.SelectionUpdateListener;
import jflowmap.util.piccolo.PButton;
import jflowmap.views.AbstractView;
import jflowmap.views.Disposable;
import jflowmap.views.MapBackgroundImage;
import jflowmap.views.flowmap.ColorSchemeAware;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.activities.PActivity;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.util.PBounds;

public class VisualHeatMap extends PNode implements SelectionUpdateListener {
	public static final ColorSchemes sequentialColorScheme = ColorSchemes.OrRd;
	public static final ColorSchemes divergingColorScheme = ColorSchemes.RdBu5;

	private final SourceLayer sourceLayer;
	private final MapLayer targetLayer;
	private final HeatMapLayer heatMapLayer;
	private final HeatMapView heatMapView;
	private final List<AbstractLayer> layers = Lists.newArrayListWithExpectedSize(3);
	
	private final Graph graph;
	
	public HeatMapLayer getHeatMapLayer() {
		return heatMapLayer;
	}
	
	public HeatMapView getHeatMapView() {
		return heatMapView;
	}

	public VisualHeatMap(final AbstractView view, Graph graph, MapBackgroundImage mapImage, 
			MapProjection proj, GeoMap areaMap, HeatMapView heatMapView) {

		System.out.println("Graph: " + graph);

		this.heatMapView = heatMapView;
		this.graph = graph;

		heatMapView.getView().getSelection("standard").select("VALUE", Sets.newHashSet(graph.getValueDimensions()));
		heatMapView.getView().getSelection("standard").addSelectionUpdateListener(this);
		
		this.sourceLayer = new SourceLayer(this, 0.3);
		this.targetLayer = new MapLayer(this, 0.3, mapImage, proj, areaMap);
		this.heatMapLayer = new HeatMapLayer(this, 0.4);
		
		layers.add(sourceLayer);
		layers.add(heatMapLayer);
		layers.add(targetLayer);
		
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				initialize();
			}
		});	
	}
	
	private void addLayerButtons() {
		final PButton heatMapSizeButton = new PButton("< - >", true);
		final PButton sourceSizeButton = new PButton("< - >", true);
		
		heatMapLayer.getLayerMenuPanel().addButton(heatMapSizeButton, new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				if (heatMapSizeButton.isPressed()) {
					heatMapSizeButton.setText("> - <");
					sourceSizeButton.setText("< - >");
					sourceSizeButton.setPressed(false);
					setRelativeLayerWidth(heatMapLayer, 0.8);
				} else {
					heatMapSizeButton.setText("< - >");
					setRelativeLayerWidth(heatMapLayer, 0.4);
				}
			}
		});
	
		sourceLayer.getLayerMenuPanel().addButton(sourceSizeButton, new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				if (sourceSizeButton.isPressed()) {
					sourceSizeButton.setText("> - <");
					heatMapSizeButton.setText("< - >");
					heatMapSizeButton.setPressed(false);
					setRelativeLayerWidth(sourceLayer, 0.7);
				} else {
					sourceSizeButton.setText("< - >");
					setRelativeLayerWidth(heatMapLayer, 0.4);
				}
			}
		});
		
		final PButton unselectSourcesButton = new PButton("Auswahl aufheben", false);
		sourceLayer.getLayerMenuPanel().addButton(unselectSourcesButton, new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				VisualHeatMap.this.getHeatMapView().getView().getSelection("standard")
				.empty("SOURCE");
			}
		});
		
		final PButton unselectTargetsButton = new PButton("Auswahl aufheben", false);
		targetLayer.getLayerMenuPanel().addButton(unselectTargetsButton, new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				VisualHeatMap.this.getHeatMapView().getView().getSelection("standard")
				.empty("TARGET");
			}
		});
	}

	private final PropertyChangeListener LAYOUT_LISTENER = new PropertyChangeListener() {
		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			if (evt.getPropertyName().equals(PCamera.PROPERTY_BOUNDS)) {
				layoutChildren();
				for (AbstractLayer layer: layers) {
					layer.fitChildrenInView();
				}
			}
		}
	};

	private void initialize() {
		heatMapView.getCamera().addPropertyChangeListener(LAYOUT_LISTENER);
		layoutChildren();
		addLayerButtons();
		
		for (AbstractLayer layer: layers) {
			layer.fitChildrenInView();
		}
	}

	public void removeNodesFromCamera() {
		final PCamera camera = heatMapView.getCamera();
		camera.removeChild(sourceLayer.getCamera());
		camera.removeChild(targetLayer.getCamera());
		camera.removeChild(heatMapLayer.getCamera());
		camera.removePropertyChangeListener(LAYOUT_LISTENER);
	}

	public void addNodesToCamera() {
		final PCamera camera = heatMapView.getCamera();
		camera.addChild(sourceLayer.getCamera());
		camera.addChild(targetLayer.getCamera());
		camera.addChild(heatMapLayer.getCamera());
	}

	@Override
	protected void layoutChildren() {
		final PBounds viewBounds = heatMapView.getCamera().getViewBounds();
		final double width = viewBounds.width;
		final double height = viewBounds.height;
		
		double x = 0;
		for (AbstractLayer layer: layers) {
			layoutCamera(layer.getCamera(), x, 0.0, width * layer.getRelativeWidth(), height);
			x += width * layer.getRelativeWidth();
			layer.getCamera().animateViewToCenterBounds(layer.getFullBounds(), false, 1000);
			layer.updateMenuPosition();
		}
	}
	
	private void setRelativeLayerWidth (AbstractLayer layer, double relativeWidth) {
		assert(relativeWidth < 1.0 && relativeWidth > 0.0);
		
		final double otherWidth = (1 - relativeWidth) / 2;
		
		for (AbstractLayer l: layers) {
			if (l != layer) l.setRelativeWidth(otherWidth);
		}
		
		layer.setRelativeWidth(relativeWidth);
		this.layoutChildren();
	}
	
	private void layoutCamera(final PCamera camera, final double x, final double y,
			final double width, final double height) {
		camera.setBounds(x, y, width, height);
	}

	public ColorSchemeAware getMapColorScheme() {
		return heatMapView.getMapColorScheme();
	}

	public Graph getGraph() {
		return this.graph;
	}

	public void update() {
		heatMapLayer.update();
		heatMapLayer.repaint();
		sourceLayer.update();
		sourceLayer.repaint();
	}

	public void fitChildrenInView() {
		for (AbstractLayer layer: layers) {
			layer.fitChildrenInView();
		}
	}

	@Override
	public void beforeSelectionChanged() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void afterSelectionChanged() {
		this.update();
	}

	@Override
	public void dispose() {
		heatMapView.getView().getSelection("standard").removeSelectionUpdateListener(this);
		heatMapLayer.dispose();
		targetLayer.dispose();
		sourceLayer.dispose();
	}

	@Override
	public void selectedSelection(SelectionDefinition definition) {
		if (definition == null) {
			heatMapLayer.getInfo().remove("Auswahl:");
		} else {
			heatMapLayer.getInfo().set("Auswahl:", definition.getName());
		}
	}

}
